package utils

import (
	"gitee.com/ymofen/gobase/gotask/grouptask"
	"time"
)

/*
**

	基于timewheel

	由于ticker对比并不精准:
		注意, 时间轮要小于1s, 最好小于500ms
		获取next执行时间时,要截取掉毫秒, 避免对比时任务未到, 下次对比时出现被忽略的情况.

	回调如果阻塞会影响时间轮任务的触发
	Seconds Minutes Hours DayofMonth Month DayofWeek Year
*/
type Cron struct {
	tw *TimeWheel
}

type CronSchedule struct {
	terminated  bool
	cron        *Cron
	taskfn      func()
	exp         CronExp
	nextT       time.Time
	getTimeFunc func() time.Time
}

func (this *CronSchedule) onRun() {
	if this.terminated {
		return
	}
	now := time.Now()
	if now.Before(this.nextT) { // 避免提前执行,
		// 直接插入到下一个槽
		this.cron.tw.AddTaskNextSlot(this, this.onRun)
		// fmt.Printf("before task time, ignore, now:%s, next:%s\n", DateTimeString3(time.Now()), DateTimeString3(this.nextT))
		return
	}
	this.insertNext(time.Second)
	this.taskfn()
}

func (this *CronSchedule) Stop() {
	this.terminated = true
	cron := this.cron
	if cron != nil {
		this.cron = nil
		cron.tw.RemoveByID(this, nil)
	}
}

func (this *CronSchedule) GetNextTime() time.Time {
	return this.nextT
}

func (this *CronSchedule) GetExp() string {
	return this.exp.text
}

func (this *CronSchedule) insertNext(span time.Duration) {
	cron := this.cron
	if cron != nil {
		t1 := this.getTimeFunc()
		if span > 0 {
			t1 = t1.Add(span + cron.tw.rollt)
		} else {
			t1 = t1.Add(cron.tw.rollt)
		}
		this.nextT = this.exp.GetNextTime(t1).Truncate(cron.tw.rollt)
		cron.tw.AddTaskNextTime(this, this.onRun, this.nextT)
		this.nextT = this.nextT.Truncate(time.Second)
	}
}

func NewCron() *Cron {
	rval := &Cron{}
	rval.tw = NewTW(time.Millisecond * 250) // 比1s要小

	/***
	  回调必须是异步, 不能阻塞掉tw的触发线程, 在onRun中有插入事件
	*/
	rval.tw.ConfigExecTaskFunc(rval.func_ExecTask_GroupSync)
	rval.start()
	return rval
}

func (this *Cron) StatusString() string {
	return ""
}

/*
**

	回调必须是异步, 不能阻塞掉tw的触发线程, 在onRun中有插入事件
*/
func (this *Cron) func_ExecTask_GroupSync(rec *TWTaskRec) {
	grouptask.DefaultGroupTask().PostTaskFunc(this, rec.DoTask)
}

func (this *Cron) start() {
	go this.tw.Start()
}

func (this *Cron) Stop() {
	this.tw.Stop()
	this.tw.WaitFor()
}

func (this *Cron) AddSchedule(exp string, task func()) (sch *CronSchedule, err error) {
	sch = &CronSchedule{cron: this, getTimeFunc: time.Now, taskfn: task, terminated: false}
	err = sch.exp.ParseText(exp)
	if err != nil {
		return
	}
	sch.insertNext(time.Second)
	return
}

func (this *Cron) AddScheduleUtc(exp string, task func()) (sch *CronSchedule, err error) {
	sch = &CronSchedule{cron: this, getTimeFunc: func() time.Time {
		return time.Now().UTC()
	}, taskfn: task, terminated: false}
	err = sch.exp.ParseText(exp)
	if err != nil {
		return
	}
	sch.insertNext(time.Second)
	return
}
